![]() |
![]() |
|
Damit befindet sich das Formular zwar im Hauptspeicher, auf dem Bildschirm erscheint es aber noch nicht. Die Komponenten der Form sind zu diesem Zeitpunkt allerdings bereits initialisiert. Zur Anzeige muss noch die Methode Show der Form aufgerufen werden:
Das Gegenstück zu Show ist Hide. Nach dem Aufruf dieser Methode ist die Form zwar nicht mehr sichtbar, bleibt aber weiter im Hauptspeicher und kann ohne Neuinstanzierung mit Show erneut angezeigt werden.
Dasselbe Ergebnis erreichen Sie auch durch das Setzen der Eigenschaft Visible der Form auf True bzw. False. Zum Schließen und Entladen einer Form dient die Methode Close:
Alle drei Methodenaufrufe haben das Auslösen einer Ereigniskette zur Folge, wie sie weiter oben beschrieben ist. 15.8.3 Mehrere Fenster verwalten
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Public Shared Sub Main() |
| Application.Run(New Form1()) |
| End Sub |
Run richtet eine Nachrichtenschleife für das Fenster (Startfenster) ein, dessen Referenz übergeben wird. Die Nachrichtenschleife wird geschlossen, wenn die als Argument übergebene Form entladen wird, denn ein Automatismus fügt dem FormClosed-Ereignis der Form implizit einen Ereignishandler hinzu, der seinerseits Application.ExitThread aufruft. Mit ExitThread wird der aktuelle Thread beendet und damit auch die Laufzeit der Anwendung.
Dieses Verhalten hat weit reichende Konsequenzen. Wird nämlich zur Laufzeit einer Anwendung ein weiteres Fenster mit
| Dim frm As New Form2 |
| frm.Show() |
instanziert und angezeigt, gliedert es sich in die laufende Nachrichtenschleife ein und teilt sich diese mit dem Startfenster. Das bedeutet aber nicht, dass das neue Fenster gleichberechtigt ist. Vielmehr hat nur das Hauptfenster die Verantwortung darüber, wann die Nachrichtenschleife geschlossen wird – nämlich genau dann, wenn es selbst geschlossen wird. Die Folge ist, dass jedes Fenster der Anwendung geschlossen wird, das zu dieser Nachrichtenschleife gehört. Mit anderen Worten formuliert:
|
Jede Form-Instanz einer Anwendung, die nicht als Argument dem Aufruf der Application.Run-Methode übergeben wurde, ist abhängig von der Lebensdauer des Startfensters. |
Dieses Verhalten ist nicht immer wünschenswert. Wenn Sie auf den Automatismus der Nachrichtenschleife verzichten wollen, müssen Sie deren Steuerung selbst in die Hand nehmen. Dabei genügt es nicht, zuerst das Startformular zu instanzieren und danach mit der Methode Run eine Nachrichtenschleife einzurichten:
| Dim frm As New Form1 |
| frm.Show() |
| Application.Run() |
Bei dieser Lösung sind Nachrichtenschleife und Startfenster nicht aneinander gekoppelt, aber es fehlt definitiv eine Anweisung, um die Nachrichtenschleife zu beenden. Die Anwendung läuft auch nach dem Schließen des letzten Fensters Fenster im Hintergrund weiter. Das können Sie sehr schön nach dem Start der Anwendung aus der Entwicklungsumgebung heraus erkennen: das Visual Studio schaltet nicht in den Entwicklungsmodus zurück.
Prinzipiell ist die Lösung des Problems trivial, denn die Nachrichtenschleife muss genau dann mit Application.Exit bzw. Application.ExitThread beendet werden, wenn das letzte Fenster geschlossen wird. Es stellt sich damit die Frage: Wie können wir feststellen, wann das letzte Fenster geschlossen wird?
Zu eine der sinnvollsten Erweiterungen, die das .NET Framework 2.0 aufzuweisen hat, gehört die Klasse FormCollection. Jede Form, die geöffnet wird, trägt sich in eine Instanz dieser Klasse ein, die jede Windowsanwendung automatisch bereitstellt. Es gibt keine Methoden in FormCollection, um die enthaltenen Forms zu verwalten. Sie werden also die üblichen Methoden Add, Remove usw. vermissen. Über Count lässt sich lediglich die Anzahl der enthaltenen Forms ermitteln und über den Indexer können Sie auf die einzelnen Forms zugreifen.
Die Referenz auf die interne Formsauflistung liefert die statische Eigenschaft OpenForms der Klasse Application. Damit ist der Lösung unseres Problems aus dem letzten Abschnitt vorgezeichnet. Zur Erinnerung: Wir hatten die feste Bindung zwischen dem Startfenster und der Nachrichtenschleife durch Aufruf der parameterlosen Überladung von Application.Run aufgehoben und mussten nur noch einen Mechanismus finden, um nach dem Schließen der letzten Form auch die Nachrichtenschleife zu beenden.
Jetzt ist der Rest sehr einfach. Im Ereignis FormClosed prüfen wir, ob mit der aktuellen Form auch die letzte der laufenden Anwendung geschlossen wird, der Zähler des FormCollection-Objekts also 0 liefert. In diesem Fall wird entweder die Methode Exit oder ExitThread von Application aufgerufen, die bekanntlich auch der Nachrichtenschleife ein Ende setzen.
| Private Sub Form1_FormClosed(...) Handles MyBase.FormClosed |
| If Application.OpenForms.Count = 0 Then |
| Application.Exit() |
| End If |
| End Sub |
Diese Anweisungen müssen im FormClosed-Ereignishandler codiert werden, da der Zähler zwischen den beiden Ereignissen FormClosing und FormClosed herabgesetzt wird.
Application.OpenForm liefert über den Index die Referenz auf eine geöffnete Form. Dazu können Sie entweder einen Integer oder einen String übergeben. Der Integer beschreibt die Position in der nullbasierten Auflistung. Meistens werden Sie damit jedoch nicht viel Erfolg haben, weil Sie in der Regel die Position des Fensters in der Collection nicht kennen. Besser hingegen ist die Übergabe einer Zeichenfolge. Diese beschreibt den Titelleistentext der gesuchten Form.
Um ein bestimmtes Fenster in der Auflistung mittels des Titelleistentextes wieder zu finden müssen Sie eigentlich nur noch dessen Eindeutigkeit gewährleisten. Im folgenden Beispiel wird das gezeigt. Das Programm enthält zwei Forms: Die erste beschreibt das Startfenster, die zweite ein Dokumentenfenster. Das Dokumentenfenster hat eine Textbox, die den gesamten Arbeitsbereich der Form für sich beansprucht. Hier kann der Anwender einen beliebigen Text eingeben.
Ein Dokumentenfenster wird aus dem Startfenster heraus geöffnet. Es können sogar beliebig viele sein. Jedes Dokumentenfenster trägt seinen Titelleistentext in eine Listbox (Name = lstDocuments) der Hauptform ein und auch wieder aus, wenn es geschlossen wird. Wenn der Anwender in der Listbox ein Dokument selektiert und anschließend auf eine Schaltfläche klickt, wird der Inhalt des Dokumentenfensters von einer Textbox im Hauptfenster übernommen.

Hier klicken, um das Bild zu Vergrößern
Abbildung 15.14 Die Fenster des Beispiels »FormCollection«
Sehen wir uns zuerst den Code des Dokumentenfensters sowie die Klasse general an, die eine statische Variable enthält.
| ' ---------------------------------------------------------- |
| ' Beispiel: ...\Kapitel 15\FormCollection |
| ' ---------------------------------------------------------- |
| Public Class Document |
| Private Sub Document_Load(...) Handles MyBase.Load |
| ' Titelleiste festlegen |
| Me.Text = "Document" & General.DocCounter |
| General.DocCounter += 1 |
| ' Titelleistentext zur Listbox des Hauptfensters hinzufügen |
| CType(Application.OpenForms(0), _ |
| Form1).lstDocuments.Items.Add(Me.Text) |
| End Sub |
| Private Sub Document_FormClosed(...) Handles MyBase.FormClosed |
| CType(Application.OpenForms(0), _ |
| Form1).lstDocuments.Items.Remove(Me.Text) |
| End Sub |
| End Class |
| Public Class General |
| Public Shared DocCounter As Integer = 1 |
| End Class |
Im Load-Ereignishandler wird die eindeutige Beschriftung der Titelleiste des Objekts gewährleistet. Dazu ist in der Klasse General die statische Variable DocCounter deklariert.
Anschließend wird der Titelleistentext in die Listbox des Hauptfensters eingetragen. Die Referenz auf das Startfenster liefert der erste FormCollection-Eintrag (Index = 0). Die zurückgelieferte Referenz muss noch in den passenden Typ (Form1) konvertiert werden, bevor der Titelleistentext der Listbox (Name = lstDocuments) übergeben werden kann. Auf den Code, der direkt mit der ListBox zusammenhängt, gehen wir an dieser Stelle nicht ein. Vergessen werden darf nicht, den Zähler DocCounter zu erhöhen.
Aus der FormCollection trägt sich das Dokumentenfenster automatisch aus, wenn es geschlossen wird. Die Listbox des Hauptfensters wird davon aber nichts mitbekommen und muss eine entsprechende Benachrichtigung erhalten. Diese wird bei Auslösung des Ereignisses FormClosed verschickt.
Jetzt folgt noch der Code des Hauptfensters.
| Public Class Form1 |
| Private Sub btnOpenDocument_Click(...) _ |
| Handles btnOpenDocument.Click |
| ' neues Dokumentenfenster öffnen |
| Dim frm As Document = New Document() |
| frm.Show() |
| End Sub |
| Private Sub btnGetText_Click(...) Handles btnGetText.Click |
| ' Inhalt des in der Listbox ausgewählten |
| ' Dokumentenfensters übernehmen |
| Try |
| Dim index As Integer = Me.lstDocuments.SelectedIndex + 1 |
| Me.txtDocumentContent.Text = _ |
| CType(Application.OpenForms(index), Document).txtText.Text |
| Catch ex As InvalidCastException |
| MessageBox.Show("Sie haben kein Dokument ausgewählt.", "Fehler") |
| Catch ex As Exception |
| MessageBox.Show(ex.Message, "Fehler") |
| End Try |
| End Sub |
| Private Sub btnBeenden_Click(...) Handles btnBeenden.Click |
| Application.Exit() |
| End Sub |
| End Class |
Die beiden Ereignishandler, die die Click-Ereignisse der mit Dokument öffnen und Beenden beschrifteten Schaltflächen behandeln, bedürfen keiner weiteren Erläuterung.
Etwas genauer sehen wir uns aber den Ereignishandler btnGetText_Click an. Zur Laufzeit soll der Anwender ein Dokument in der Listbox auswählen. Alle Elemente in einer Listbox werden von einer Collection verwaltet, so dass wir nur noch den Index des selektierten Elements bestimmen müssen. Dieser wird in der lokalen Variablen index gespeichert. index übergeben wir an Application.OpenForms und haben nach entsprechender Konvertierung den Zugriff auf die Textbox des Dokumentenfensters.
Anwender machen nie das, was Sie sollen oder dürfen. Das müssen wir abschließend noch berücksichtigen. Wählt der Benutzer keinen Listboxeintrag aus, wäre eine Ausnahme die Folge. Daher wird der Code des Ereignishandlers in einen Try-Block gefasst.
Sie können auch für jede Form eine eigene Nachrichtenschleife vorsehen. Da eine Nachrichtenschleife an einen Thread gebunden ist bedeutet das, dass Sie für das entsprechende Fenster einen weiteren Thread starten müssen. Im folgenden Beispielprogramm sehen Sie das prinzipielle Vorgehen.
| ' ---------------------------------------------------------- |
| ' Beispiel: ...\Kapitel 15\FormMultithreading |
| ' ---------------------------------------------------------- |
| Imports System.Threading |
| Public Class Form1 |
| Private Sub btnNewForm_Click(...) Handles btnNewForm.Click |
| Dim thread As Thread = _ |
| New Thread(New ThreadStart(AddressOf startNewForm)) |
| thread.Start() |
| End Sub |
| ' neues Fenster in einem eigenen Thread starten |
| Private Sub startNewForm() |
| Application.Run(New Form2()) |
| End Sub |
| End Class |
Jedes Fenster vom Typ Form2 soll jetzt in einer eigenen Nachrichtenschleife gestartet werden. Im Ereignishandler der Schaltfläche wird dazu zuerst eine neue Thread-Instanz erzeugt. Dieser wird die Adresse der Methode übergegeben, die beim Start des Threads aufgerufen werden soll (im Beispiel handelt es sich um startNewForm). Anschließend wird der neue Thread mit Start ins Leben gerufen:
Die Methode startNewForm enthält nur eine Anweisung:
| Application.Run(New Form2()) |
Die Folge des Aufrufs ist nicht nur ein neuer Thread, sondern gleichzeitig auch eine zweite, von der ersten vollkommen unabhängige Nachrichtenschleife. Wird der Ereignishandler mehrfach ausgeführt, erhöht sich die Anzahl der Fenster und gleichzeitig auch die Anzahl der laufenden Threads.
Soll die Anwendung unabhängig von der Anzahl der geöffneten Fenster sofort beendet werden, ist die statische Methode Exit der Klasse Application passend. Während ExitThread nur den aktuellen Thread zerstört, werden mit Exit alle Threads beendet und infolgedessen wird auch die Laufzeit der Anwendung. ExitThread führt nur dann zum Beenden der Anwendung, wenn es sich um den letzten Thread handelt.
Manchmal nimmt die Initialisierung einer Anwendung längere Zeitspanne in Anspruch. Um den Anwender nicht darüber im Unklaren zu lassen, ob das Programm tatsächlich startet oder nicht, wird während dieser Phase informationshalber ein Fenster angezeigt, das als Splash-Fenster bezeichnet wird. Fenster dieser Art können Sie auf zweierlei Art und Weise bereitstellen:
| Sie benutzen eine vom Visual Studio 2005 bereitgestellte Vorlage, die bereits alle wesentlichen Merkmale eines Splash-Fensters aufweist. |
| Sie programmieren das Begrüßungsfenster selbst. |
Um die Anwendung um ein Splash-Fenster zu erweitern, öffnen Sie das Kontextmenü des Projekts im Projektmappen-Explorer und wählen Hinzufügen/Neues Element. Wählen Sie aus der Liste der Vorlagen Begrüßungsbildschirm aus. Der Vorteil der Vorlage ist, dass alle Eigenschaften bereits passend eingestellt sind.

Hier klicken, um das Bild zu Vergrößern
Abbildung 15.15 Die Vorlage »Begrüßungsbildschirm«
Die Form enthält keine Titelleiste, den Arbeitsbereich teilen sich zwei Label- und ein MainLayoutPanel-Steuerelement. Diese müssen Sie Ihren Wünschen entsprechend einstellen. Damit das Begrüßungsfenster auch tatsächlich vor dem Hauptfenster der Anwendung angezeigt wird, müssen Sie dieses in den Projekteigenschaften unter Begrüßungsbildschirm einstellen. Die Folge ist, dass beim Start des Programms das Splash-Fenster mindestens zwei Sekunden lang angezeigt wird, ehe es vom Hauptfenster abgelöst wird. Dauert die Initialisierung des Hauptfensters länger, ist das Splash-Fenster solange sichtbar, bis das Hauptfenster erscheint.
Die Mindestanzeigezeit des Splash-Fensters können Sie mit der Eigenschaft MinimumSplashScreenDisplayTime des MyApplication-Objekts modifizieren. Öffnen Sie dazu die Klassendefinition, die sich in der Datei ApplicationEvents.vb befindet und geben Sie den folgenden Code ein:
| Partial Friend Class MyApplication |
| Protected Overrides Function OnInitialize(...) As Boolean |
| ' Anzeigedauer 10 Sekunden einstellen |
| Me.MinimumSplashScreenDisplayTime = 10000 |
| Return MyBase.OnInitialize(commandLineArgs) |
| End Function |
| End Class |
Splash-Fenster haben keine Titelleiste Um ein Fenster ohne Titelleiste zu erzeugen, darf die Eigenschaft Text der Form keinen Eintrag haben und ControlBox auf muss False eingestellt sein. Die Anzeigeposition ist üblicherweise mittig auf dem Bildschirm, StartPosition ist also FormStartPosition.CenterScreen.
Das Beispielprogramm SplashDemo weist einen solches Splash-Fenster auf. Der Code ist in der manuell bereitgestellten Methode Main implementiert. Im Projekteigenschaftsfenster muss daher Sub Main als Startobjekt ausgewählt werden.
| ' ---------------------------------------------------------- |
| ' Beispiel: ...\Kapitel 15\SplashDemo |
| ' ---------------------------------------------------------- |
| Imports System.Threading |
| Public Class Program |
| Public Shared Sub Main() |
| Application.EnableVisualStyles() |
| Dim frmSplash As New Splash |
| frmSplash.Show() |
| Application.DoEvents() |
| Try |
| For i As Integer = 0 To 100 |
| ' Initialisierungsarbeiten |
| frmSplash.label2.Text = "Initialisierung: ... " & i |
| frmSplash.Label2.Refresh() |
| ' throw-Anweisung dient nur zum Testen |
| ' throw new Exception(); |
| Thread.Sleep(50) |
| Next |
| Catch |
| MessageBox.Show("Beim Starten ist ein Fehler aufgetreten.", _ |
| Application.ProductName, MessageBoxButtons.OK, _ |
| MessageBoxIcon.Error) |
| Application.Exit() |
| End Try |
| frmSplash.Close() |
| Application.Run(New Form1()) |
| End Sub |
| End Class |
Damit die Initialisierungsarbeiten, die im Beispiel durch eine For-Schleife simuliert werden, nicht beginnen, bevor das Fenster vollständig gezeichnet ist, rufen wir vor der Schleife Application.DoEvents auf. (Hinweis: Application.DoEvents nehmen wir weiter unten in diesem Kapitel genauer unter die Lupe).
In der Praxis können während der Initialisierung Fehler auftreten – beispielsweise wenn mit der Initialisierung ein Datenbank- oder Dateizugriff verbunden ist. Deshalb werden die Initialisierungsarbeiten in einem Try-Block ausgeführt. Tritt eine Exception auf, wird der Anwender benachrichtigt und die Anwendung geschlossen. Um die Fehlerbehandlung zu testen, enthält die Schleife des Beispiels eine Throw-Anweisung, deren Auskommentierung nur aufgehoben werden muss.
Startet die Anwendung fehlerfrei, muss das Splash-Fenster mit Close geschlossen werden. Dann ist auch der Zeitpunkt gekommen, das Hauptfenster der Anwendung mit Application.Run zu öffnen.
| << zurück |
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
Copyright © Galileo Press 2007
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken.
Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die
gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich
geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung,
Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.